﻿// MIT License
// 开源地址：https://gitee.com/co1024/AbcMvc
// Copyright (c) 2021-2023 1024
// Abc.Mvc=Furion+EF+SqlSugar+Pear layui admin.

using Yitter.IdGenerator;

namespace Abc.Utils;

/// <summary>
///     web 文件上传
/// </summary>
public class WebUpLoad
{
    /// <summary>
    ///     单文件上传
    /// </summary>
    /// <param name="input">输入信息</param>
    /// <param name="file"></param>
    /// <returns></returns>
    public static async Task<WebUploadResult> FileUpload(WebUploadInput input, IFormFile file)
    {
        return await FileUploadItem(input, file);
    }

    /// <summary>
    ///     单文件上传
    /// </summary>
    /// <param name="input">输入信息</param>
    /// <returns></returns>
    public static async Task<WebUploadResult> FileUpload(WebUploadInput input)
    {
        var context = App.GetService<IHttpContextAccessor>().HttpContext;
        if (context == null || context.Request.Form.Files == null || context.Request.Form.Files.Count <= 0)
            throw Oops.Bah("请先选择文件");
        var file = context.Request.Form.Files[0];
        return await FileUploadItem(input, file);
    }

    /// <summary>
    ///     多个文件上传
    /// </summary>
    /// <param name="input">输入信息</param>
    /// <returns></returns>
    public static async Task<List<WebUploadResult>> FileUploads(WebUploadInput input)
    {
        var context = App.GetService<IHttpContextAccessor>().HttpContext;
        if (context == null || context.Request.Form.Files == null || context.Request.Form.Files.Count <= 0)
            throw Oops.Bah("请先选择文件");
        var resultdata = new List<WebUploadResult>();
        foreach (var file in context.Request.Form.Files)
            try
            {
                var rd = await FileUploadItem(input, file);
                resultdata.Add(rd);
            }
            catch (Exception ex)
            {
                if (file != null)
                    resultdata.Add(new WebUploadResult { uploadFilename = file.FileName, msg = ex.Message });
            }

        return resultdata;
    }

    /// <summary>
    ///     多个文件上传
    /// </summary>
    /// <param name="input">输入信息</param>
    /// <param name="files">文件集合</param>
    /// <returns></returns>
    public static async Task<List<WebUploadResult>> FileUploads(WebUploadInput input, List<IFormFile> files)
    {
        var resultdata = new List<WebUploadResult>();
        foreach (var file in files)
            try
            {
                var rd = await FileUploadItem(input, file);
                resultdata.Add(rd);
            }
            catch (Exception ex)
            {
                if (file != null)
                    resultdata.Add(new WebUploadResult { uploadFilename = file.FileName, msg = ex.Message });
            }

        return resultdata;
    }

    /// <summary>
    ///     单文件上传
    /// </summary>
    /// <param name="input">输入信息</param>
    /// <returns></returns>
    private static async Task<WebUploadResult> FileUploadItem(WebUploadInput input, IFormFile file)
    {
        var webUploadOut = new WebUploadResult();


        var context = App.GetService<IHttpContextAccessor>().HttpContext;

        var newfilepath = "";

        if (file == null || string.IsNullOrEmpty(file.FileName)) throw Oops.Bah("请先选择文件");

        #region 判断大小

        //imgFile.Length (单位字节)
        var maxlength = input.MaxFileSize * 1024 * 1024; //转换为字节
        //long mb = imgFile.Length / 1024 / 1024; // MB
        if (file.Length > maxlength)
            //resultdata.Errors = ;
            //return resultdata;
            throw Oops.Bah($"上传文件超过{input.MaxFileSize}MB");
        webUploadOut.fileLength = file.Length; //字节

        #endregion 判断大小

        var filename = file.FileName;
        //var filename = ContentDispositionHeaderValue
        //                .Parse(file.ContentDisposition)
        //                .FileName.Trim("").Trim("\"");

        //var extname = filename[filename.LastIndexOf(".")..]; //扩展名，如.jpg
        webUploadOut.extension = filename.GetExtension().ToLower(); //获取扩展名
        webUploadOut.uploadFilename = filename;


        if (!string.IsNullOrWhiteSpace(input.AllowExtensions) && !input.AllowExtensions.ToLower().Trim()
                .Contains(webUploadOut.extension.Trim().ToLower()))
            throw Oops.Bah($"文件上传格式错误，只允许上传{input.AllowExtensions}格式的文件或图片！");


        var webUploadSettingOptions = AppEx.GetConfig<WebUploadSettingOptions>();
        if (webUploadSettingOptions == null) webUploadSettingOptions = new WebUploadSettingOptions();
        //验证是否在全局设定的上传的文件中
        if (webUploadSettingOptions.IsLimitUploadExtension
            && webUploadSettingOptions.AllowExtensions != null
            && !webUploadSettingOptions.AllowExtensions.Contains(webUploadOut.extension,
                StringComparer.OrdinalIgnoreCase))
            throw Oops.Bah("不允许上传此文件类型！");

        //验证文件类型
        if (webUploadSettingOptions.IsFileTypeCheck)
        {
            var isFileTypeCheck = FileTypeCheck.IsValidFileExtension(file, webUploadOut.extension);
            if (!isFileTypeCheck)
                throw Oops.Bah($"文件上传格式错误，只允许上传{webUploadSettingOptions.AllowExtensions?.ToString(",")}格式的文件或图片！");
        }

        //开启了 压缩检查图片，才验证
        //验证图片是否是真正的图片
        if (webUploadSettingOptions.IsCompressCheckImage
            && webUploadSettingOptions.ImgExtensions != null
            && webUploadSettingOptions.ImgExtensions.Contains(webUploadOut.extension, StringComparer.OrdinalIgnoreCase))
        {
            var isimage = SkiaSharpHelper.IsImage(file);
            if (!isimage) throw Oops.Bah($"文件上传格式错误，只允许上传{webUploadSettingOptions.ImgExtensions.ToString(",")}格式的图片！");
        }


        #region 文件类别判断

        //if (!extname.ToLower().Contains("jpg") && !extname.ToLower().Contains("png") && !extname.ToLower().Contains("gif"))
        //{
        //    return Json(new { code = 1, msg = "只允许上传jpg,png,gif格式的图片.", });
        //}

        //switch (input.FileType)
        //{
        //    case WebUploadType.所有:
        //        {
        //            //if (!string.IsNullOrWhiteSpace(input.LimitExtensions) && !input.LimitExtensions.ToLower().Trim().Contains(extname.Trim().ToLower()))
        //            //{
        //            //    throw Oops.Bah($"文件上传格式错误，只允许上传{input.AllowExtensions}格式的文件！");
        //            //}
        //        }
        //        break;

        //    case WebUploadType.自定义:
        //        {

        //        }
        //        break;

        //    case WebUploadType.图片:
        //        {
        //            if (!string.IsNullOrWhiteSpace(input.AllowExtensions) && !input.AllowExtensions.ToLower().Trim().Contains(webUploadOut.extension.Trim().ToLower()))
        //            {
        //                throw Oops.Bah($"文件上传格式错误，只允许上传{input.AllowExtensions}格式的图片！");
        //            }
        //        }
        //        break;

        //    case WebUploadType.Office文件:
        //        {
        //            if (!string.IsNullOrWhiteSpace(input.AllowExtensions) && !input.AllowExtensions.ToLower().Trim().Contains(webUploadOut.extension.Trim().ToLower()))
        //            {
        //                throw Oops.Bah($"文件上传格式错误，只允许上传{input.AllowExtensions}格式的文件！");
        //            }
        //        }
        //        break;

        //    case WebUploadType.压缩包:
        //        {
        //            if (!string.IsNullOrWhiteSpace(input.AllowExtensions) && !input.AllowExtensions.ToLower().Trim().Contains(webUploadOut.extension.Trim().ToLower()))
        //            {
        //                throw Oops.Bah($"文件上传格式错误，只允许上传{input.AllowExtensions}格式的文件！");
        //            }
        //        }
        //        break;

        //    default:
        //        break;
        //}

        #endregion 文件类别判断

        //var filename1 = "";//文件名
        ////如果传入的文件名为空，则按 {DateTime.Now:yyyyMMddHHmmss}{RandomHelper.GetInt(1000, 9909)} 生成
        //if (string.IsNullOrWhiteSpace(input.FileNamePrefix))
        //{
        //    filename1 = $"{DateTime.Now:yyyyMMddHHmmss}{RandomHelper.GetInt(1000, 9909)}{webUploadOut.extension}".ToLower();
        //}
        //else
        //{
        //    //按：{input.FileName}_{DateTime.Now:yyyyMMddHHmmss}  生成
        //    filename1 = $"{input.FileNamePrefix}_{DateTime.Now:yyyyMMddHHmmss}{webUploadOut.extension}".ToLower();
        //}
        //webUploadOut.newFilename = filename1;

        //生成文件名
        GenerateFileName(input, file, webUploadOut);


        // 日期目录：yyyyMMdd
        var dir = DateTime.Now.ToString("yyyyMMdd");
        if (string.IsNullOrWhiteSpace(input.FloderName)) input.FloderName = "DefaultFiles"; //设置默认文件夹

        var path1 = ""; //默认相对路径，不包含目录和文件名
        if (input.IsDateTimeFloder)
            // upload/{typeName}/20210101
            path1 = Path.Combine(input.BaseFloder, input.FloderName.TrimStart('\\'), dir.TrimStart('\\'));
        else
            // upload/{typeName}
            path1 = Path.Combine(input.BaseFloder, input.FloderName.TrimStart('\\'));

        //默认物理文件夹
        //var defaultPhysicalPath = App.Configuration["AppInfo:WebUploadFloder"];
        if (string.IsNullOrWhiteSpace(input.DefaultPhysicalPath) || !Directory.Exists(input.DefaultPhysicalPath))
            //如果不存在，设置默认的物理文件，网站更目录下
            input.DefaultPhysicalPath = App.WebHostEnvironment.ContentRootPath;

        //完整物理路径，注意要用分隔符
        var wuli_path = input.DefaultPhysicalPath + $"{Path.DirectorySeparatorChar}{path1}";
        if (!Directory.Exists(wuli_path)) Directory.CreateDirectory(wuli_path);
        newfilepath = wuli_path + Path.DirectorySeparatorChar + webUploadOut.newFilename;

        using (var fs = File.Create(newfilepath))
        {
            await file.CopyToAsync(fs);
            fs.Flush();
        }

        //图片压缩、生成缩略图
        ImgCompress(input, file, webUploadOut, webUploadSettingOptions, wuli_path, newfilepath, path1);


        webUploadOut.newFilePath = newfilepath;


        //默认文件夹：type=DefaultFiles
        //默认相对路径：/DefaultFiles/20220408/xxxx文件夹名.png
        //默认物理路径为：网站根目录/upload/DefaultFiles/20220408/xxxx文件夹名.png
        webUploadOut.src = HandlePathOrUrl($"{path1}\\{webUploadOut.newFilename.Replace("/", "\\")}");
        //默认：http://域名/DefaultFiles/20220408/xxxx文件夹名.png
        webUploadOut.httpsrc = context?.GetDomain()?.TrimEnd('/') + "/" +
                               HandlePathOrUrl($"{path1.TrimStart('/')}/{webUploadOut.newFilename}");


        webUploadOut.issuccess = true; //表示上传成功了

        return webUploadOut;
    }

    /// <summary>
    ///     图片压缩
    /// </summary>
    /// <param name="input"></param>
    /// <param name="file"></param>
    /// <param name="webUploadOut"></param>
    /// <param name="webUploadSettingOptions"></param>
    /// <param name="wuli_path">物理路径</param>
    /// <param name="newfilepath">新的文件名</param>
    /// <param name="relativePath">相对路径，不包含文件名</param>
    private static void ImgCompress(WebUploadInput input, IFormFile file, WebUploadResult webUploadOut
        , WebUploadSettingOptions webUploadSettingOptions
        , string wuli_path
        , string newfilepath
        , string relativePath)
    {
        if (webUploadSettingOptions.ImgExtensions != null
            && webUploadSettingOptions.ImgExtensions.Contains(webUploadOut.extension, StringComparer.OrdinalIgnoreCase))
        {
            if (input.IsImgCompression || webUploadSettingOptions.IsImgCompression)
                try
                {
                    var newfilepath2 = "";
                    //单独配置的
                    if (input.IsImgCompression)
                    {
                        GenerateFileName(input, file, webUploadOut);
                        newfilepath2 = wuli_path + Path.DirectorySeparatorChar + webUploadOut.newFilename;
                        SkiaSharpHelper.Compress(newfilepath, newfilepath2, 0, input.ImgCompressionRatio);
                    }
                    //系统配置的
                    else if (webUploadSettingOptions.IsImgCompression)
                    {
                        GenerateFileName(input, file, webUploadOut);
                        newfilepath2 = wuli_path + Path.DirectorySeparatorChar + webUploadOut.newFilename;
                        SkiaSharpHelper.Compress(newfilepath, newfilepath2, 0,
                            webUploadSettingOptions.ImgCompressionRatio);
                    }

                    //尝试删除原图文件
                    FileHelper.TryDeleteFile(newfilepath);

                    newfilepath = newfilepath2;
                }
                catch (Exception ex)
                {
                }

            //生成缩略图
            {
                try
                {
                    if (FileHelper.IsExistFile(newfilepath))
                        //是否生成缩略图
                        if (input.IsImgThumbnail || webUploadSettingOptions.IsImgThumbnail)
                        {
                            //缩略图宽度
                            var ImgThumbnailWidth = 0;
                            if (input.IsImgThumbnail)
                                ImgThumbnailWidth = input.ImgThumbnailWith;
                            else if (webUploadSettingOptions.IsImgThumbnail)
                                ImgThumbnailWidth = webUploadSettingOptions.ImgThumbnailWith;

                            //缩略图名字
                            var thumbnailFileName = webUploadOut.newFilename.Replace(file.FileName.GetExtension(), "") +
                                                    $"_thumbnail{file.FileName.GetExtension()}";

                            var thumbnail_newfilepath2 = wuli_path + Path.DirectorySeparatorChar + thumbnailFileName;

                            SkiaSharpHelper.CompressAndCut(newfilepath, thumbnail_newfilepath2, ImgThumbnailWidth, 0, 0,
                                30);

                            webUploadOut.thumbnail =
                                HandlePathOrUrl($"{relativePath}\\{webUploadOut.newFilename.Replace("/", "\\")}");
                        }
                }
                catch (Exception ex)
                {
                }
            }
        }
    }

    private static void GenerateFileName(WebUploadInput input, IFormFile file, WebUploadResult webUploadOut)
    {
        //如果传入的文件名为空，则按 {DateTime.Now:yyyyMMddHHmmss}_{文件ID}{扩展名} 生成
        if (string.IsNullOrWhiteSpace(input.FileName))
            webUploadOut.newFilename =
                $"{DateTime.Now:yyyyMMddHHmmss}_{webUploadOut.FId}{webUploadOut.extension}".ToLower();
        else
            //按：{DateTime.Now:yyyyMMddHHmmss}_{文件ID}_{input.FileName}{扩展名}  生成
            webUploadOut.newFilename =
                $"{DateTime.Now:yyyyMMddHHmmss}_{webUploadOut.FId}_{input.FileName}{webUploadOut.extension}".ToLower();
    }


    /// <summary>
    ///     处理路径或url地址
    /// </summary>
    /// <param name="str"></param>
    /// <returns></returns>
    private static string HandlePathOrUrl(string str)
    {
        for (var i = 0; i < 3; i++) str = str.Replace("\\\\", "/").Replace("\\", "/").Replace("//", "/");
        return str;
    }
}

/// <summary>
///     web上传输入
/// </summary>
public class WebUploadInput
{
    public WebUploadInput()
    {
    }

    /// <summary>
    ///     构造函数
    /// </summary>
    /// <param name="allowExtensions">允许扩展名</param>
    /// <param name="limitExtensions">限制扩展名（默认禁止 html/xml 扩展名上传）</param>
    /// <param name="maxFileSize">最大文件大小(单位：MB),默认10MB</param>
    /// <param name="baseFloder">默认根目录文件夹：upload</param>
    /// <param name="FloderName">文件夹,type</param>
    /// <param name="fileName">文件名</param>
    /// <param name="IsDateTimeFloder">是否按日期创建文件夹</param>
    /// <param name="IsDateTimeFloder">是否使用逻辑路径  /u/f</param>
    public WebUploadInput(string allowExtensions = "", string limitExtensions = ".html,.xml", int maxFileSize = 10,
        string baseFloder = "upload", string FloderName = "DefaultFiles", string fileName = "",
        bool IsDateTimeFloder = true)
    {
        AllowExtensions = allowExtensions;
        LimitExtensions = limitExtensions;
        MaxFileSize = maxFileSize;
        this.FloderName = FloderName;
        FileName = fileName;
        this.IsDateTimeFloder = IsDateTimeFloder;
    }

    /// <summary>
    ///     允许扩展名(如果为空，表示默认所有此类型扩展)
    /// </summary>
    public string AllowExtensions { get; set; }

    /// <summary>
    ///     限制扩展名(文件类型为全部，生效)
    ///     默认禁止 html/xml 扩展名上传
    /// </summary>
    public string LimitExtensions { get; set; } = ".html,.xml";

    /// <summary>
    ///     默认物理路径(网站根目录)
    /// </summary>
    public string DefaultPhysicalPath { get; set; } =
        AppEx.GetUploadDefaultFloder(); //App.WebHostEnvironment.ContentRootPath;

    /// <summary>
    ///     默认根目录文件夹：upload
    /// </summary>
    public string BaseFloder { get; set; } = "Upload";

    ///// <summary>
    ///// 文件上传类型
    ///// </summary>
    //public WebUploadType FileType { get; set; } = WebUploadType.所有;

    /// <summary>
    ///     最大文件大小(单位：MB)
    /// </summary>
    public int MaxFileSize { get; set; } = 10;

    /// <summary>
    ///     文件夹
    /// </summary>
    public string FloderName { get; set; } = "DefaultFiles";

    ///// <summary>
    ///// 默认文件输出逻辑路径
    ///// URL访问的逻辑路径 域名/逻辑路径/文件相对路径
    ///// 这样可以隐藏真正的服务器文件路径
    ///// </summary>
    //public string LogicalPath { get; set; } = "/u/f";

    ///// <summary>
    ///// 是否使用逻辑路径  /u/f
    ///// </summary>
    //public bool IsUseLogicalPath { get; set; } = false;

    /// <summary>
    ///     是否按日期创建文件夹
    ///     例如：FloderName\20220101\文件名
    /// </summary>
    public bool IsDateTimeFloder { get; set; } = true;

    /// <summary>
    ///     文件名前缀
    ///     默认按：{DateTime.Now:yyyyMMddHHmmss}{RandomHelper.GetInt(1000, 9909)
    /// </summary>
    public string FileName { get; set; }

    /// <summary>
    ///     是否启用图片压缩
    /// </summary>
    public bool IsImgCompression { get; set; } = false;

    /// <summary>
    ///     图片压缩率，整数，1~100
    /// </summary>
    public int ImgCompressionRatio { get; set; } = 90;


    /// <summary>
    ///     是否生成图片缩略图
    /// </summary>
    public bool IsImgThumbnail { get; set; } = false;

    /// <summary>
    ///     缩略图宽度，高度按比例计算
    /// </summary>
    public int ImgThumbnailWith { get; set; } = 0;
}

/// <summary>
///     web上传文件类型
/// </summary>
public enum WebUploadType
{
    所有 = 0,
    自定义 = 1,
    图片 = 2,
    Office文件 = 3,
    压缩包 = 4
}

/// <summary>
///     Web 上传返回
/// </summary>
public class WebUploadResult
{
    public long FId { get; set; } = YitIdHelper.NextId();

    /// <summary>
    ///     文件名
    /// </summary>
    public string uploadFilename { get; set; }

    /// <summary>
    ///     扩展名
    /// </summary>
    public string extension { get; set; }

    /// <summary>
    ///     新文件名
    /// </summary>
    public string newFilename { get; set; }

    /// <summary>
    ///     新文件路径
    /// </summary>
    public string newFilePath { get; set; }

    /// <summary>
    ///     文件大小（字节）
    /// </summary>
    public long fileLength { get; set; }

    /// <summary>
    ///     相对路径
    /// </summary>
    public string src { get; set; }

    /// <summary>
    ///     http完整路径
    /// </summary>
    public string httpsrc { get; set; }

    /// <summary>
    ///     是否成功
    /// </summary>
    public bool issuccess { get; set; }

    /// <summary>
    ///     消息
    /// </summary>
    public string msg { get; set; }

    /// <summary>
    ///     缩略图
    /// </summary>
    public string thumbnail { get; set; }

    /// <summary>
    ///     获取默认返回结果
    /// </summary>
    /// <returns></returns>
    public dynamic GetDefaultResult()
    {
        return new { src, httpsrc, issuccess, msg };
    }
}